﻿using System;
using System.Linq;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Windows.Input;

namespace Taste.Demos.Silverlight
{
    public class MainViewModel : INotifyPropertyChanged
    {
        public enum State { LoadingData, ViewingData, CreatingData, EditingData, SavingData, Failed }
        public enum Trigger { View, Create, Edit, Save, Cancel, AcknowledgeError }

        private StateMachine<State, Trigger> _stateMachine;
        private DataModel.CustomerContext _customerContext;

        public MainViewModel()
        {
            _customerContext = new DataModel.CustomerContext();

            _stateMachine = new StateMachine<State, Trigger>();
            _stateMachine.CurrentStateChanged += (s, e) => this.RaisePropertyChanged("CurrentState");

            _stateMachine.DefineState(State.LoadingData, WhenLoadingData, Transitions.FollowUserData<State>());
            _stateMachine.DefineState(State.ViewingData, WhenViewingData, Transitions.FollowUserData<State>());
            _stateMachine.DefineState(State.CreatingData, WhenCreatingData, Transitions.FollowUserData<State>());
            _stateMachine.DefineState(State.EditingData, WhenEditingData, Transitions.FollowUserData<State>());
            _stateMachine.DefineState(State.SavingData, WhenSavingData, Transitions.FollowUserData<State>());
            _stateMachine.DefineState(State.Failed, WhenFailed, Transitions.FollowUserData<State>());

            _stateMachine.OnErrorDefault(Errors.HandleIn(State.Failed));
        }

        public void OnNavigatedTo()
        {
            _stateMachine.Start(State.LoadingData);
        }

        private void WhenLoadingData(ICurrentState<State, Trigger> state)
        {
            _customerContext.LoadCustomers(customers =>
            {
                this.Customers = new ObservableCollection<DataModel.Customer>();
                foreach(var customer in customers)
                    this.Customers.Add(customer);

                state.Leave(State.ViewingData);
            });
        }

        private void WhenViewingData(ICurrentState<State, Trigger> state)
        {
            this.CurrentCustomer = this.CurrentCustomer ?? this.Customers.FirstOrDefault();

            state.OnFired(Trigger.View, delegate(DataModel.Customer c) { this.CurrentCustomer = c; });
            state.OnFired(Trigger.Create, () => state.Leave(State.CreatingData));
            state.OnFired(Trigger.Edit, () => state.Leave(State.EditingData));
        }

        private void WhenCreatingData(ICurrentState<State, Trigger> state)
        {
            var newCustomer = new DataModel.Customer();
            this.CurrentCustomer = newCustomer;

            newCustomer.PropertyChanged += RaiseCanSaveWhenIsValidChanged;
            state.BeforeNextState(() => newCustomer.PropertyChanged -= RaiseCanSaveWhenIsValidChanged);

            state.OnFired(Trigger.Save, () =>
            {
                this.Customers.Add(this.CurrentCustomer);
                state.Leave(State.SavingData);
            },
            () => this.CurrentCustomer.IsValid);

            state.OnFired(Trigger.Cancel, () =>
            {
                this.CurrentCustomer = null;
                state.Leave(State.ViewingData);
            });
        }

        private void WhenEditingData(ICurrentState<State, Trigger> state)
        {
            DataModel.Customer backupCustomer = new DataModel.Customer();
            backupCustomer.FirstName = this.CurrentCustomer.FirstName;
            backupCustomer.LastName = this.CurrentCustomer.LastName;

            this.CurrentCustomer.PropertyChanged += RaiseCanSaveWhenIsValidChanged;
            state.BeforeNextState(() => this.CurrentCustomer.PropertyChanged -= RaiseCanSaveWhenIsValidChanged);

            state.OnFired(Trigger.Save, () =>
            {
                //throw new Exception("This is a test exception.");
                state.Leave(State.SavingData);
            },
            () => this.CurrentCustomer.IsValid);

            state.OnFired(Trigger.Cancel, () =>
            {
                this.CurrentCustomer.FirstName = backupCustomer.FirstName;
                this.CurrentCustomer.LastName = backupCustomer.LastName;
                state.Leave(State.ViewingData);
            });          
        }

        private void RaiseCanSaveWhenIsValidChanged(object sender, PropertyChangedEventArgs e)
        {
            if(e.PropertyName == "IsValid")
                _stateMachine.GetTrigger(Trigger.Save).RaiseCanFireChanged();
        }

        private void WhenSavingData(ICurrentState<State, Trigger> state)
        {
             //throw new Exception("This is another test exception.");

            _customerContext.SaveCustomers(this.Customers, () =>
            {
                state.Leave(State.ViewingData);
            });
        }

        private void WhenFailed(ICurrentState<State, Trigger> state)
        {
            Exception error = state.UserData as Exception;
            this.ErrorMessage = (error != null ? error.Message : "Unknown error");

            state.OnFired(Trigger.AcknowledgeError, () => state.Leave(State.ViewingData));
        }

        public State CurrentState { get { return _stateMachine.CurrentState.Name; }}
        public ICommand ViewCommand { get { return _stateMachine.GetTrigger(Trigger.View).AsCommand(); } }
        public ICommand CreateCommand { get { return _stateMachine.GetTrigger(Trigger.Create).AsCommand(); } }
        public ICommand EditCommand { get { return _stateMachine.GetTrigger(Trigger.Edit).AsCommand(); } }
        public ICommand SaveCommand { get { return _stateMachine.GetTrigger(Trigger.Save).AsCommand(); } }
        public ICommand CancelCommand { get { return _stateMachine.GetTrigger(Trigger.Cancel).AsCommand(); } }
        public ICommand AcknowledgeErrorCommand { get { return _stateMachine.GetTrigger(Trigger.AcknowledgeError).AsCommand(); } }

        #region Observable properties
        private ObservableCollection<DataModel.Customer> _Customers;
        public ObservableCollection<DataModel.Customer> Customers
        {
            get { return _Customers; }
            set { if(_Customers != value) { _Customers = value; RaisePropertyChanged("Customers"); } }
        }

        private DataModel.Customer _CurrentCustomer;
        public DataModel.Customer CurrentCustomer
        {
            get { return _CurrentCustomer; }
            set { if(_CurrentCustomer != value) { _CurrentCustomer = value; RaisePropertyChanged("CurrentCustomer"); } }
        }

        private string _ErrorMessage;
        public string ErrorMessage
        {
            get { return _ErrorMessage; }
            set { if(_ErrorMessage != value) { _ErrorMessage = value; RaisePropertyChanged("ErrorMessage"); } }
        }
        #endregion  
      
        #region INotifyPropertyChanged members
        public event PropertyChangedEventHandler PropertyChanged;

        protected void RaisePropertyChanged(string propertyName)
        {
            if(this.PropertyChanged != null)
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        } 
        #endregion
    }
}
